// @ts-nocheck

"use strict";

import { Context, IResponseBodyData } from "egg";
import { getRouteMetaData, routeSymbolKey } from "../../../decorator/routeMetadata";

/** dinegg
 * 特殊的controller为了使用装饰器，替换使用egg-core内的初始化方法
 * 复制egg-core中的loader controller进行改造的，如果官方修改了该文件，也需要跟随一起改。
 */

const path = require("path");
const is = require("is-type-of");
const utility = require("utility");
const utils = require("egg-core/lib/utils");
const FULLPATH = require("egg-core/lib/loader/file_loader").FULLPATH;

const controllerMappingSymbol = Symbol.for("dinegg#controllerMapping");
const roleSymbolKey = Symbol.for("dinegg#controllerRole");

/** dinegg框架改造后的控制器入口，
 * - 支持装饰器绑定路由
 * - 控制器返回结果作为body响应
 *  */
export default function (obj, opt) {
	// return class if it exports a function
	// ```js
	// module.exports = app => {
	//   return class HomeController extends app.Controller {};
	// }
	// ```
	if (
		is.function(obj) &&
		!is.generatorFunction(obj) &&
		!is.class(obj) &&
		!is.asyncFunction(obj)
	) {
		obj = obj(this.app);
	}
	if (is.class(obj)) {
		obj.prototype.pathName = opt.pathName;
		obj.prototype.fullPath = opt.path;
		return wrapClass(obj);
	}
	if (is.object(obj)) {
		return wrapObject(obj, opt.path);
	}
	// support generatorFunction for forward compatbility
	if (is.generatorFunction(obj) || is.asyncFunction(obj)) {
		return wrapObject({ "module.exports": obj }, opt.path)["module.exports"];
	}
	return obj;
}

// wrap the class, yield a object with middlewares
function wrapClass(Controller) {
	let proto = Controller.prototype;
	const ret = {};
	// tracing the prototype chain
	while (proto !== Object.prototype) {
		const keys = Object.getOwnPropertyNames(proto);
		for (const key of keys) {
			// getOwnPropertyNames will return constructor
			// that should be ignored
			if (key === "constructor") {
				continue;
			}
			// skip getter, setter & non-function properties
			const d = Object.getOwnPropertyDescriptor(proto, key);
			// prevent to override sub method
			if (is.function(d.value) && !ret.hasOwnProperty(key)) {
				ret[key] = methodToMiddleware(Controller, key);
				ret[key][FULLPATH] =
					Controller.prototype.fullPath + "#" + Controller.name + "." + key + "()";
				// 24.10.26 使用Reflect获取绑定的路由，但注意只有最外层控制器类才行，继承类如果有装饰器绑定路由，则不会生效
				const routes = getRouteMetaData(Controller);
				ret[key][routeSymbolKey] = routes[key] || [];

				// note 支持在控制器类构造函数上绑定一个控制器通用的路由mapping 类型为字符串数组
				ret[controllerMappingSymbol] = Controller[controllerMappingSymbol];
			}
		}
		proto = Object.getPrototypeOf(proto);
	}
	return ret;

	function methodToMiddleware(Controller, key) {
		return function classControllerMiddleware(this: Context, ...args) {
			const controller = new Controller(this);
			// console.log(args);
			if (!this.app.config.controller || !this.app.config.controller.supportParams) {
				args = [this];
			}
			// 不要egg的参数，直接传入常用的body,query,params
			args = [this.request.body, this.request.query, this.params];

			const ctrls = utils
				.callFn(controller[key], args, controller)
				.then((res?: IResponseBodyData) => {
					// console.log("ctrls", res);
					if (res) {
						controller.ctx.body = res;
					}
					return res;
				});

			return ctrls;
		};
	}
}

// wrap the method of the object, method can receive ctx as it's first argument
function wrapObject(obj, path, prefix) {
	const keys = Object.keys(obj);
	const ret = {};
	for (const key of keys) {
		if (is.function(obj[key])) {
			const names = utility.getParamNames(obj[key]);
			if (names[0] === "next") {
				throw new Error(
					`controller \`${
						prefix || ""
					}${key}\` should not use next as argument from file ${path}`
				);
			}
			ret[key] = functionToMiddleware(obj[key]);
			ret[key][FULLPATH] = `${path}#${prefix || ""}${key}()`;
		} else if (is.object(obj[key])) {
			ret[key] = wrapObject(obj[key], path, `${prefix || ""}${key}.`);
		}
	}
	return ret;

	function functionToMiddleware(func) {
		const objectControllerMiddleware = async function (...args) {
			if (!this.app.config.controller || !this.app.config.controller.supportParams) {
				args = [this];
			}
			return await utils.callFn(func, args, this);
		};
		for (const key in func) {
			objectControllerMiddleware[key] = func[key];
		}
		return objectControllerMiddleware;
	}
}
